use crate::db::error::DbErr;
use crate::db::po::{ActiveModelExt, ColumnExt};
use sea_orm::{
    ActiveModelBehavior, ActiveModelTrait, DatabaseConnection, EntityTrait, FromQueryResult,
    IntoActiveModel, QueryFilter, QueryOrder, QuerySelect, Set, entity::prelude::*,
};
use sea_query::{IntoCondition, Order, SimpleExpr};

pub struct OrderBy {
    pub column: SimpleExpr,
    pub order: Order,
}

#[derive(Default)]
pub struct FindParams<T>
where
    T: Default + PartialEq + Clone,
{
    pub filter: T,
    pub offset: Option<u64>,
    pub limit: Option<u64>,
    pub order_by: Option<OrderBy>,
}

#[async_trait::async_trait]
pub trait BaseDao: Send + Sync {
    type Model: Send + IntoActiveModel<Self::ActiveModel> + FromQueryResult;
    type ActiveModel: Send
        + ActiveModelExt
        + ActiveModelBehavior
        + ActiveModelTrait<Entity = Self::Entity>;
    type Entity: EntityTrait<Model = Self::Model, ActiveModel = Self::ActiveModel>;
    type Column: ColumnExt + ColumnTrait;
    type Filter: Send + IntoCondition + Eq + PartialEq + Default + Clone;

    fn db(&self) -> &DatabaseConnection;
    async fn insert(&self, model: Self::ActiveModel) -> Result<(), DbErr> {
        let mut model = model.clone();
        if model.create_timestamp_millis().is_not_set() {
            model.set_create_timestamp_millis(Set(wanmesh_common::util::now_timestamp_millis()));
        }

        if model.update_timestamp_millis().is_not_set() {
            model.set_update_timestamp_millis(model.create_timestamp_millis())
        }

        model.insert(self.db()).await.map(|_| ())?;

        Ok(())
    }

    async fn find(&self, params: FindParams<Self::Filter>) -> Result<Vec<Self::Model>, DbErr> {
        let mut db = Self::Entity::find();

        if params.filter != Self::Filter::default() {
            db = db.filter(params.filter);
        }

        if let Some(offset) = params.offset {
            db = db.offset(offset);
        }

        if let Some(limit) = params.limit {
            db = db.limit(limit);
        }

        if let Some(order_by) = params.order_by {
            db = db.order_by(order_by.column, order_by.order);
        }

        let list = db.all(self.db()).await?;

        Ok(list)
    }

    async fn try_get(&self, filter: Self::Filter) -> Result<Option<Self::Model>, DbErr> {
        let m = Self::Entity::find().filter(filter).one(self.db()).await?;
        Ok(m)
    }
    async fn delete(&self, filter: Self::Filter) -> Result<(), DbErr> {
        Self::Entity::delete_many()
            .filter(filter)
            .exec(self.db())
            .await
            .map(|_| ())?;
        Ok(())
    }

    async fn count(&self, filter: Self::Filter) -> Result<u64, DbErr> {
        let db = Self::Entity::find()
            .select_only()
            .expr(Self::Column::id().count());
        let count: i64 = if filter != Self::Filter::default() {
            db.filter(filter)
        } else {
            db
        }
        .into_tuple()
        .one(self.db())
        .await?
        .map_or(0, |count| count);
        Ok(count as u64)
    }

    async fn exists(&self, filter: Self::Filter) -> Result<bool, DbErr> {
        let count = self.count(filter).await?;
        Ok(count > 0)
    }
}

#[async_trait::async_trait]
pub trait UpdateDao: BaseDao {
    type UpdateParams: Into<Self::ActiveModel> + Default + PartialEq + Send;
    async fn update(&self, filter: Self::Filter, params: Self::UpdateParams) -> Result<(), DbErr> {
        if params == Self::UpdateParams::default() {
            return Ok(());
        }

        let mut model: Self::ActiveModel = params.into();
        if model.update_timestamp_millis().is_not_set() {
            model.set_update_timestamp_millis(Set(wanmesh_common::util::now_timestamp_millis()));
        }
        Self::Entity::update_many()
            .set(model)
            .filter(filter)
            .exec(self.db())
            .await
            .map(|_| ())?;

        Ok(())
    }
}
